package br.iars.manager.impl;

import java.util.LinkedList;
import java.util.List;

import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;

import br.iars.manager.ContextManager;
import br.iars.manager.exception.BusinessException;
import br.iars.manager.exception.SystemException;
import br.iars.manager.util.RateConverter;
import br.iars.model.context.ContextDataModel;
import br.iars.model.entity.Context;
import br.iars.model.entity.Item;
import br.iars.model.entity.Preference;
import br.iars.model.entity.RateType;
import br.iars.model.entity.User;
import br.iars.model.exception.PersistenceException;
import br.iars.model.recommendation.RecommendationDataModel;

@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class ContextManagerImpl implements ContextManager {

	@EJB
	private ContextDataModel contextDataModel;

	@EJB
	private RecommendationDataModel recommendationDataModel;

	public void addContext(String companyName, String systemName, Float minRate, Float maxRate, String rateType) throws BusinessException,
			SystemException {
		Context context = new Context(companyName, systemName, minRate, maxRate, RateType.fromValue(rateType));

		try {
			Context c = contextDataModel.load(context);
			if (c != null) {
				context.setId(c.getId());
				contextDataModel.update(context);
			} else {
				contextDataModel.insert(context);
			}
		} catch (PersistenceException e) {
			throw new SystemException("An error ocurred when the system add a context", e);
		}
	}

	public void setPreference(String companyName, String systemName, String userEmail, Long itemCode, Float rate) throws BusinessException,
			SystemException {

		Context context = loadContext(companyName, systemName);

		if (userEmail == null)
			throw new BusinessException("Invalid user's email!");
		if (itemCode == null)
			throw new BusinessException("Invalid item's code!");
		if (rate == null)
			throw new BusinessException("Invalid rate value!");
		if (rate < context.getMinRate() || rate > context.getMaxRate())
			throw new BusinessException("Rate cannot be less than " + context.getMinRate() + " or greater than " + context.getMaxRate() + "!");
		if (context.getRateType().equals(RateType.INTEGER)) {
			if (rate.intValue() != rate) {
				throw new BusinessException("Rate value for your context should be an integer number!");
			}
		}

		User user = loadUser(userEmail);
		Item item = loadItem(context, itemCode);

		Preference preference = new Preference(user, item, RateConverter.getUncontextualizedValue(context, rate));
		try {
			recommendationDataModel.setPreference(preference);
		} catch (PersistenceException e) {
			throw new SystemException("An error ocurred when the system set preference of an user", e);
		}
	}

	public void setPreferences(String companyName, String systemName, List<Preference> preferences) throws BusinessException, SystemException {
		Context context = loadContext(companyName, systemName);

		for (Preference preference : preferences) {
			if (preference.getUser() == null || preference.getUser().getEmail() == null)
				throw new BusinessException("Invalid user's email!");
			if (preference.getItem() == null || preference.getItem().getCode() == null)
				throw new BusinessException("Invalid item's code!");
			if (preference.getRate() == null)
				throw new BusinessException("Invalid rate value!");
			if (preference.getRate() < context.getMinRate() || preference.getRate() > context.getMaxRate())
				throw new BusinessException("Rate cannot be less than " + context.getMinRate() + " or greater than " + context.getMaxRate() + "!");
			if (context.getRateType().equals(RateType.INTEGER)) {
				if (preference.getRate().intValue() != preference.getRate()) {
					throw new BusinessException("Rate value for your context should be an integer number!");
				}
			}
			User user = loadUser(preference.getUser().getEmail());
			Item item = loadItem(context, preference.getItem().getCode());
			preference.setUser(user);
			preference.setItem(item);
			preference.setRate(RateConverter.getUncontextualizedValue(context, preference.getRate()));
		}

		try {
			for (Preference preference : preferences) {
				recommendationDataModel.setPreference(preference);
			}
		} catch (PersistenceException e) {
			throw new SystemException("An error ocurred when the system set preference of an user", e);
		}
	}

	public List<Preference> recommend(String companyName, String systemName, String userEmail, Integer quantity, Boolean isOnlyOwnContext)
			throws BusinessException, SystemException {

		Context context = loadContext(companyName, systemName);

		if (userEmail == null)
			throw new BusinessException("Invalid user's email!");
		if (quantity == null)
			throw new BusinessException("Invalid quantity's value!");

		User user = loadUser(userEmail);

		try {
			List<Preference> preferences = recommendationDataModel.recommend(context, user, quantity, isOnlyOwnContext);

			for (Preference preference : preferences) {
				preference.setItem(contextDataModel.load(preference.getItem()));
				preference.setRate(RateConverter.getContextualizedValue(context, preference.getRate()));
			}
			return preferences;
		} catch (PersistenceException e) {
			throw new SystemException("An error ocurred when the system recommed a item", e);
		}

	}

	public Float estimatePreference(String companyName, String systemName, String userEmail, Long itemCode, Boolean isOnlyOwnContext)
			throws BusinessException, SystemException {

		Context context = loadContext(companyName, systemName);

		if (userEmail == null)
			throw new BusinessException("Invalid user's email!");
		if (itemCode == null)
			throw new BusinessException("Invalid item's code!");

		User user = loadUser(userEmail);
		Item item = loadItem(context, itemCode);

		try {
			Float rate = recommendationDataModel.estimatePreference(context, user, item, isOnlyOwnContext);
			return RateConverter.getContextualizedValue(context, rate);
		} catch (PersistenceException e) {
			throw new SystemException("An error ocurred while the system estimate a preference", e);
		}
	}

	public List<User> getNeighbors(String companyName, String systemName, String userEmail, Boolean isOnlyOwnContext) throws BusinessException,
			SystemException {

		Context context = loadContext(companyName, systemName);

		if (userEmail == null)
			throw new BusinessException("Invalid user's email!");

		User user = loadUser(userEmail);

		try {
			long[] users = recommendationDataModel.getNeighbors(context, user, isOnlyOwnContext);

			List<User> neighbors = new LinkedList<User>();
			
			if (users != null){
				for (long userId : users){
					neighbors.add(contextDataModel.load(new User(userId)));
				}
			}			
			
			return neighbors;
		} catch (PersistenceException e) {
			throw new SystemException("An error ocurred while the system estimate a preference", e);
		}
	}

	private Context loadContext(String companyName, String systemName) throws BusinessException, SystemException {
		try {
			if (companyName == null)
				throw new BusinessException("Invalid company's name!");
			if (systemName == null)
				throw new BusinessException("Invalid system's name!");

			Context context = contextDataModel.load(new Context(companyName, systemName));
			if (context == null) {
				throw new BusinessException("Invalid context!");
			}

			return context;
		} catch (PersistenceException e) {
			throw new SystemException(e.getMessage(), e);
		}
	}

	private Item loadItem(Context context, Long itemCode) throws SystemException {
		try {
			Item item = contextDataModel.load(new Item(itemCode, context));

			if (item == null) {
				item = new Item(itemCode, context);
				item = contextDataModel.insert(item);
			}

			return item;
		} catch (PersistenceException e) {
			throw new SystemException(e.getMessage(), e);
		}
	}

	private User loadUser(String userEmail) throws SystemException {
		try {
			User user = contextDataModel.load(new User(userEmail));

			if (user == null) {
				user = new User(userEmail);
				user = contextDataModel.insert(user);
			}

			return user;
		} catch (PersistenceException e) {
			throw new SystemException(e.getMessage(), e);
		}
	}

}
